/*
    Author: code@darkc0de.org

    NOTES: This version of Palimpsest is using a series of
    TLS packets using the Diffie-Hellman exchange with 128
    bit AES in CBC mode and a SHA-1 hash for the first
    exchange of the initialization vector. While this is
    still not as secure as the Rijndael exchange that will
    eventually be implemented, it is *significantly* more
    secure than the SSL_DH_anon_WITH_RC4_128_MD5 exchange
    that was being used in previous versions.
    
    If you can feasibly crack the IV, please email me and 
    let me know- palimpse@darkc0de.org
    
    TODO: Clean up the code a bit more, improve the user
    interface, implement a Rijndael exchange instead of an
    imperfect TLS packet, maybe write up a man page for new
    users.
    
    HELP: If you're great at creating fairly simple Java
    GUIs (such as a frontend for Palimpsest), please email
    me and let me know (code@darkc0de.org). I can do it on
    my own, but I'm a busy little coder- it'll take me
    awhile. If you can help, let me know.
    
    THANKS: Thanks to lain for helping me find problems in
    java.net and OutputStream.write(byte[]) and for misc.
    debugging help, gtech for being awed with me all the time
    and 2600net for letting me ramble about my code, and
    asking me fun questions.
    
    This code is licensed under the GPL v2.
*/

import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.util.*;

public class palimpse
{
  public static void main(String[] args)
  {
    String version = "0.3.6 - beta 1\n\tReport any bugs to palimpse@darkc0de.org\n";
    Scanner sec = new Scanner(System.in);
    byte[] SecureKey = new byte[4096];
    byte[] OldKey = new byte[4096];
    byte[] ciphertext = new byte[4096];
    String plaintext = "";
    byte[] KeyPacket = new byte[4096];
    final String[] enabledCipherSuites = { "TLS_DH_anon_WITH_AES_128_CBC_SHA" };
    // the above statement used to be SSL_DH_anon_WITH_RC4_128_MD5
    
    System.out.println("Palimpsest version "+version);
    
    if(args.length > 0) // client is Alice
    {
      try {
        Socket sock = new Socket(args[0], 1337);
        System.out.println("Established connection to "+args[0]);
        OutputStream out = sock.getOutputStream();
        InputStream in = sock.getInputStream();
        
          System.out.println("Establishing SSL exchange... please be patient...\n");
          
          SSLSocketFactory sslsocketfactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
          SSLSocket sslsocket = (SSLSocket)sslsocketfactory.createSocket(args[0], 1338);
          
            sslsocket.setEnabledCipherSuites(enabledCipherSuites);
          
          OutputStream outSSL = sslsocket.getOutputStream();        
        
          SecureKey = genKey();
          send(SecureKey, outSSL);
          sslsocket.close();

        
        
        while(true)
        {
          System.out.print("\n\nEnter plaintext: ");
          
          send((encrypt(SecureKey, sec.nextLine())), out);
          
          System.out.println("\n");
          
          OldKey = SecureKey;
          SecureKey = genKey();
          
          send((pack(SecureKey, OldKey)), out);
          
          decrypt((receive(ciphertext, in)), SecureKey);
          
          OldKey = SecureKey;
          
          SecureKey = unpack(receive(KeyPacket, in), OldKey); 
            
        } // end while(true)
      } // end try
      catch(IOException e)
      {
        System.out.println("Alice error: "+e);
      } // end catch
      
    } // end if (Alice)
    
    if(args.length == 0) // client is Bob
    {
      System.out.println("Waiting for connection to establish");
      try {
        ServerSocket ssock = new ServerSocket(1337);
        Socket sock = ssock.accept();
        InputStream in = sock.getInputStream();
        OutputStream out = sock.getOutputStream();
        
        System.out.println("Connection established");
        
          SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
          SSLServerSocket sslserversocket = (SSLServerSocket)sslserversocketfactory.createServerSocket(1338);
          
          sslserversocket.setEnabledCipherSuites(enabledCipherSuites);
          
          
          System.out.println("Establishing SSL connection... please be patient\n");
          SSLSocket sslsocket = (SSLSocket)sslserversocket.accept();
          
          InputStream sslIn = sslsocket.getInputStream();
          
        
          SecureKey = receive(SecureKey, sslIn);
          sslsocket.close();
          
        
        
        while(true)
        {
          decrypt((receive(ciphertext, in)), SecureKey);
          
          OldKey = SecureKey;
          SecureKey = unpack(receive(KeyPacket, in), OldKey);
                      
          System.out.print("\n\nEnter plaintext: ");
          send((encrypt(SecureKey, sec.nextLine())), out);
          
          System.out.println("\n");
          
          OldKey = SecureKey;
          SecureKey = genKey();
          send(pack(SecureKey, OldKey), out);
          
        } // end while(true)
      } // end try
      catch(IOException e)
      {
        System.out.println("Bob error: "+e);
      }
    } // end Bob
  } // end main
 
  public static byte[] genKey()
  {
    SecureRandom rand = new SecureRandom();
    byte[] key = new byte[4096]; // default size
    rand.nextBytes(key);
    return key;
  }

  public static byte[] encrypt(byte[] key, String p)
  {
    int ctr;
    byte[] c = new byte[key.length];
    SecureRandom sec = new SecureRandom();
    c[0] = (byte)(key[0] ^ (byte)p.length());
    for(ctr = 1; ctr <= p.length(); ctr++)
    {
      c[ctr] = (byte)(p.charAt(ctr-1) ^ key[ctr]);
    }
    
    byte[] temp = new byte[key.length - ctr];
    sec.nextBytes(temp);
    int ctr2 = 0;
    ctr++;
    while(ctr < key.length)
    {
      c[ctr] = (byte)(key[ctr] ^ temp[ctr2]);
      ctr2++;
      ctr++;
    }
    
    return c;
  }
  
  public static byte[] unpack(byte[] c, byte[] k)
  {
    byte[] p = new byte[c.length];
    for(int ctr = 0; ctr < c.length; ctr++)
    {
      p[ctr] = (byte)(c[ctr] ^ k[ctr]);
    }
    return p;
  }
  
  public static byte[] pack(byte[] p, byte[] k)
  {
    byte[] c = new byte[k.length];
    for(int ctr = 0; ctr < k.length; ctr++)
    {
      c[ctr] = (byte)(p[ctr] ^ k[ctr]);
    }
    return c;
  }
  
  public static void decrypt(byte[] c, byte[] k)
  {
    int length = (int)(c[0] ^ k[0]);
    for(int ctr = 1; ctr <= length; ctr++)
    {
      System.out.print((char)(c[ctr] ^ k[ctr]));
    }
  }
  
  public static void send(byte[] data, OutputStream out)
  {
    for(int ctr = 0; ctr < data.length; ctr++)
    {
    try {
      out.write(data[ctr]);
      out.flush();
    }
    catch(IOException e)
    {
      System.out.println("Send exception: "+e);
    }
    }
  }
  
  public static byte[] receive(byte[] data, InputStream in)
  {
    for(int ctr = 0; ctr < data.length; ctr++)
    {
    try {
      data[ctr] = (byte)in.read();
    }
    catch(IOException e)
    {
      System.out.println("Receive exception: "+e);
    }
    }
    return data;
  }

} // end class palimpse